#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "./TADs/heap.h"
#include "./TADs/lista.h"

#define nombreArchivoResulados	"Results.dat"
#define nombreArchivoArchivos	"archivosLista.dat"
#define nombreArchivoPalabras	"palabrasLista.dat"
#define nombreArchivoVectorConsultas	"QVector.dat"
#define NombreMatrizPonderada	"PMatrix.dat"
#define cantidadMaximaPalabras	500000
#define cantidadResultadosmostrar	5

unsigned int cantidadPalabras;

typedef struct indicePalabra
{
	unsigned long valor; //PalabraIndex
	double ponderacion;
}indicePalabra_t;

typedef struct resultado
{
	char* palabra;
	long int relevancia;
}resultado_t;

int compResultado(resultado_t* A, resultado_t* B)
{
	if (A->relevancia > B->relevancia) return 1;
	else if (A->relevancia < B->relevancia) return -1;
	return 0;
}

int compNum(indicePalabra_t* A, indicePalabra_t* B)
{
	if (A->valor > B->valor) return -1;
	else if (A->valor < B->valor) return 1;
	return 0;
}

void strLower(char str[])
{
	for (int i = 0; str[i] != '\0'; i++)
	{
		str[i] = (char)tolower(str[i]);
	}
}

void destruirVectorPalabras(char** vectorPalabras, unsigned int longitud)
{
	for (unsigned int i = 0; i < longitud; i++)
	{
		free(vectorPalabras[i]);
	}
	
	free(vectorPalabras);
}

void destruirResultado(void * resultado)
{
	resultado_t* resultadoCast = (resultado_t*) resultado;
	
	free(resultadoCast->palabra);
	free(resultadoCast);
}

void mostrarPalabras(char** vectorPalabras, unsigned int longitud)
{
	for (unsigned int i = 0; i < longitud; i++)
	{
		printf("%s\n", vectorPalabras[i]);
	}
}

char** cargarPalabras(void)
{
	char** vectorPalabras = malloc(sizeof(char*)*cantidadMaximaPalabras);
	char palabra[150];
	int posPalabra = 0;
	unsigned int posVector = 0;
	
	FILE * wordsFile;
	wordsFile = fopen(nombreArchivoPalabras, "r");
	if (wordsFile == NULL)
	{
		printf("ERROR: Todavia no se ha generado el repositorio.\n");
		free(vectorPalabras);
		return NULL;
	}
	
	char ch = fgetc( wordsFile );
	while(ch != EOF)
	{
		if (ch == '\n')
		{
			palabra[posPalabra] = '\0';
			posPalabra = 0;
			char* palabraMem = malloc((1 + sizeof(char))*strlen(palabra));
			strcpy(palabraMem, palabra);
			vectorPalabras[posVector] = palabraMem;
			posVector++;
		} else {
			palabra[posPalabra] = ch;
			posPalabra++;
		}
		
		ch = fgetc( wordsFile );
	}
	
	cantidadPalabras = posVector;
	fclose(wordsFile);
	
	return vectorPalabras;
}

unsigned int obtenerIndicePalabra(char** vectorPalabras, unsigned int longitud, 
char* palabraBuscada)
{
	// Busca la palabra con busqueda binaria aprovechando que la lista esta ordenada
	unsigned int indiceMenor = 0;
	unsigned int indiceMayor = longitud;
	unsigned int indiceActual = (indiceMayor + indiceMenor)/2;
	char* palabraActual = vectorPalabras[indiceActual];
	int direccionResultados = strcmp(palabraActual, palabraBuscada);
	
	while(direccionResultados != 0)
	{
		if (direccionResultados < 0)
		{
			indiceMenor = indiceActual;
		} else {
			indiceMayor = indiceActual;
		}
		
		indiceActual = (indiceMayor + indiceMenor)/2;
		palabraActual = vectorPalabras[indiceActual];
		
		direccionResultados = strcmp(palabraActual, palabraBuscada);
		if (indiceMenor + 1 == indiceMayor) break;
	}
	
	if (direccionResultados != 0) return longitud + 1;	// Valor prohibido
	return indiceActual + 1;
}

void ponderarPalabra(FILE* matrizPonderada, indicePalabra_t* indicePalabraActual){
	char palabra[255];
	bool agregarCaracter = true;
	bool cond = false;
	bool yaAgreguePond = false;
	bool terminado = false;
	unsigned long index = 0;

	int estado = 0;		// 0 Documento, 1 Palabra, 2 Valor

	char buffer[255];
	for (int i = 0; i < 5; i++){
		fgets(buffer, sizeof(buffer),matrizPonderada);
	}

	char c = fgetc(matrizPonderada);

	while(!cond || !terminado)
	{
		if (c == ' ' || c == '\n') agregarCaracter = false;

		if (agregarCaracter == false)
		{
			palabra[index] = '\0';
			if (estado == 0)
			{
				estado++;
			} else if (estado == 1)
			{
				if ((unsigned long) atol(palabra) == indicePalabraActual->valor) cond = true;
				else {
					if (yaAgreguePond) terminado = true;
				}
				estado++;
			} else if (estado == 2)
			{
				if (cond && !terminado){
					indicePalabraActual->ponderacion += (double) atof(palabra);
					yaAgreguePond = true;
				}
				estado = 0;
			}
			agregarCaracter = true;
			index = 0;
		} else {
			palabra[index] = c;
			index++;
		}

		c = fgetc ( matrizPonderada );
		if (c == EOF) break;
	}


}

void ponderarQuery(heap_t* indicesPalabras, char* repositorioNombre){
	double maximo = -1;
	indicePalabra_t* indicePalabraActual;
	lista_t* listaPalabras = lista_crear();

	char pathMatrizPonderada[255];
	sprintf(pathMatrizPonderada, "./%s/%s", repositorioNombre, NombreMatrizPonderada);
	FILE * matrizPonderada = fopen(pathMatrizPonderada, "r");

	while (heap_esta_vacio(indicesPalabras) == false)
	{
		// Extraigo las palabras del heap, las pondero, y las pongo en una lista
		indicePalabraActual = heap_desencolar(indicesPalabras);
		lista_insertar_ultimo(listaPalabras, indicePalabraActual);
		ponderarPalabra(matrizPonderada, indicePalabraActual);
		if (maximo < indicePalabraActual->ponderacion) maximo = indicePalabraActual->ponderacion;
	}

	lista_iter_t* iter = lista_iter_crear(listaPalabras);
	while ( ! lista_iter_al_final(iter))
	{
		// Devuelvo las palabras al heap iterando la lista
		indicePalabraActual = lista_iter_ver_actual(iter);
		if (maximo != 0){
			indicePalabraActual->ponderacion = (indicePalabraActual->ponderacion / maximo);
		} else indicePalabraActual->ponderacion = 1;

		heap_encolar(indicesPalabras, indicePalabraActual);
		lista_iter_avanzar(iter);
	}

	lista_iter_destruir(iter);
	lista_destruir(listaPalabras,NULL);
	fclose(matrizPonderada);
}

void guardarVectorConsultaEnDisco(heap_t *indicesPalabras)
{
	indicePalabra_t* indicePalabraActual;
	FILE * vectorFile;
	vectorFile = fopen(nombreArchivoVectorConsultas, "w");
	
	// Header
	fprintf(vectorFile, "# name: q\n# type: sparse matrix\n");
	fprintf(vectorFile, "# nnz: %lu\n", (long unsigned int)heap_cantidad(indicesPalabras));
	fprintf(vectorFile, "# rows: %u\n# columns: 1\n", cantidadPalabras);
	
	while (heap_esta_vacio(indicesPalabras) == false)
	{
		indicePalabraActual = heap_desencolar(indicesPalabras);
		fprintf(vectorFile, " %lu 1 %f\n", indicePalabraActual->valor, indicePalabraActual->ponderacion);
		//fprintf(vectorFile, " %lu 1 1\n", indicePalabraActual->valor);
		free(indicePalabraActual);
	}
	
	fclose(vectorFile);
}

void hacerConsulta(char* repositorioNombre)
{
	char command[120];
	sprintf(command, "octave -q --eval 'query(\"%s\")'", repositorioNombre);
	
	system(command);
}

heap_t* cargarResultados()
{
	heap_t* resultados = heap_crear( (cmp_func_t) compResultado );
	char nombreArchivo[256];
	char valorRelevancia[256];
	int posNombreArchivo = 0;
	int posValorRelevancia = 0;
	FILE * filesNamesFile;
	FILE * resultsFile;
	filesNamesFile = fopen(nombreArchivoArchivos, "r");	
	resultsFile = fopen(nombreArchivoResulados, "r");
	
	char chFiles = fgetc ( filesNamesFile );
	char chResults = fgetc ( resultsFile ); 
	
	// Me posiciono en el archivo donde empiezan los datos
	int cantidadLineasSaltear = 5;
	while (cantidadLineasSaltear > 0)
	{
		chResults = fgetc ( resultsFile );
		if (chResults == '\n') cantidadLineasSaltear--;
	}
	
	while (chFiles != EOF)
	{
		// Si ya se ha llegado al final de una palabra
		if (chFiles == '\n')
		{
			nombreArchivo[posNombreArchivo] = '\0';
			posNombreArchivo = 0;
			char* nombreArchivoMem = malloc((1 + sizeof(char))*strlen(nombreArchivo));
			strcpy(nombreArchivoMem, nombreArchivo);
			// Se busca el valor de relevancia correspondiente a esa palabra
			while(true)
			{	
				chResults = fgetc ( resultsFile );
				// Cuando ya se encontro dicho valor se guarda el elemento
				if (chResults == '\n')
				{
					valorRelevancia[posValorRelevancia] = '\0';
					posValorRelevancia = 0;
					
					resultado_t* resultado = malloc(sizeof(resultado_t));
					resultado->relevancia = atol(valorRelevancia);
					resultado->palabra = nombreArchivoMem;
					heap_encolar(resultados, resultado);
					break;
				} else {
					valorRelevancia[posValorRelevancia] = chResults;
					posValorRelevancia++;
				}
			}
			
		} else {
			nombreArchivo[posNombreArchivo] = chFiles;
			posNombreArchivo++;
		}
		
		chFiles = fgetc ( filesNamesFile );
	}
	
	fclose(filesNamesFile);
	fclose(resultsFile);
	
	return resultados;
}

void mostrarResultados(heap_t* resultados)
{
	resultado_t* resultado = heap_desencolar(resultados);
	
	for (unsigned int i = 0; i < cantidadResultadosmostrar; i++)
	{
		printf("%d. %s\tRelevancia:%0.3f\n", i + 1, resultado->palabra, (float)(resultado->relevancia)/10000000);
		destruirResultado( (void*) resultado );
		if (heap_esta_vacio(resultados) == true) break;
		resultado = heap_desencolar(resultados);
	}
	
	destruirResultado(resultado);
}

int main(int argc, char *argv[])
{
	bool existeAlgunaPalabraBuscada = false;
	int posicionPalabrasBusqueda;
	char* repositorioNombre;
	char* palabraMem = malloc(sizeof(char)*255);
	char **vectorPalabras = cargarPalabras();
	char* palabraMemCompuesta = malloc(sizeof(char)*255);
	if (vectorPalabras == NULL)
	{
		free(palabraMem);
		free(palabraMemCompuesta);
		return 0;
	}
	heap_t* indicesPalabras = heap_crear( (cmp_func_t) compNum);
	
	if (strcmp("-r", argv[1]) == 0)
	{
		repositorioNombre = argv[2];
	}
	
	for (int i = 1; i < argc; i++)
	{
		if (strcmp(argv[i], "-q") == 0)
		{
			posicionPalabrasBusqueda = i + 1;
			break;
		}
	}
	
	bool buscarSimple = true;
	unsigned int indicePalabra;
	
	for (int i = posicionPalabrasBusqueda; i < argc; i++)
	{
		strcpy(palabraMem, argv[i]);
		strLower(palabraMem);
		if (i + 1 < argc)
		{
			strcpy(palabraMemCompuesta, palabraMem);
			strcat(palabraMemCompuesta, " ");
			strcat(palabraMemCompuesta, argv[i + 1]);
			strLower(palabraMemCompuesta);
			
			indicePalabra = obtenerIndicePalabra(vectorPalabras, cantidadPalabras, palabraMemCompuesta);
			if (indicePalabra != cantidadPalabras + 1)
			{
				buscarSimple = false;
				i++;
			} else {
				buscarSimple = true;
			}
		}
		
		if (buscarSimple) indicePalabra = obtenerIndicePalabra(vectorPalabras, cantidadPalabras, palabraMem);
		if (indicePalabra == cantidadPalabras + 1)
		{
			printf("La palabra '%s' no existe en la base de datos.\n", argv[i]);
			continue;	// Esa palabra no existe
		} else {
			existeAlgunaPalabraBuscada = true;
		}
		
		indicePalabra_t* indicePalabraMem = malloc(sizeof(indicePalabra_t));
		indicePalabraMem->valor = indicePalabra;
		indicePalabraMem->ponderacion = 0;
		heap_encolar(indicesPalabras, indicePalabraMem);
		
		buscarSimple = true;
	}
	
	if (existeAlgunaPalabraBuscada == false){
		printf("\nNinguna palabra en la busqueda existe en la base de datos.\n");
		
		heap_destruir(indicesPalabras, free);
		destruirVectorPalabras(vectorPalabras, cantidadPalabras);
		free(palabraMem);
		free(palabraMemCompuesta);
		
		return -1;
	}
	
	ponderarQuery(indicesPalabras, repositorioNombre);
	guardarVectorConsultaEnDisco(indicesPalabras);
	heap_destruir(indicesPalabras, free);
	destruirVectorPalabras(vectorPalabras, cantidadPalabras);
	
	hacerConsulta(repositorioNombre);
	heap_t* resultados = cargarResultados();	
	mostrarResultados(resultados);
	
	heap_destruir(resultados, destruirResultado);
	free(palabraMem);
	free(palabraMemCompuesta);
	
	return 0;
}
